/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <arch/scr.h>
#include <stdlib/stdint.h>
#include <tools/macros.h>

#define DCACHE_INVAL_RANGE       1
#define DCACHE_CLEAN_INVAL_RANGE 2

#define CCSIDR_LINE_SIZE_OFFSET 0
#define CCSIDR_LINE_SIZE_MASK   0x7

int scr_get_cache_info(unsigned int level, unsigned int InD, int *sets, int *ways, int *linesize)
{
    uint32_t     value;
    unsigned int linesizeshift;

    if (level > SCR_CLIDR_BIT_CTYPE_COUNT || level < 1 || InD > 1) {
        return -1;
    }

    value = ((level - 1) << SCR_CSSELR_BIT_LEVEL) | (InD << SCR_CSSELR_BIT_InD);

    scr_write_CSSELR_EL1(value);

    value = scr_read_CCSIDR_EL1();

    *sets = (value >> SCR_CCSIDR_BIT_NumSets) & lowbitsmask(SCR_CCSIDR_BIT_NumSets_WIDTH);
    *sets += 1;
    *ways = (value >> SCR_CCSIDR_BIT_Associativity) & lowbitsmask(SCR_CCSIDR_BIT_Associativity_WIDTH);
    *ways += 1;
    linesizeshift = (value >> SCR_CCSIDR_BIT_LineSize) & lowbitsmask(SCR_CCSIDR_BIT_LineSize_WIDTH);
    linesizeshift += 2;

    *linesize = 1 << linesizeshift;

    return 0;
}

unsigned int scr_get_cache_type(unsigned int level)
{
    uint32_t     value;
    unsigned int type = (unsigned int)~0;

    if (level <= SCR_CLIDR_BIT_CTYPE_COUNT && level > 0) {
        value = scr_read_CLIDR_EL1();
        value >>= (level - 1) * SCR_CLIDR_BIT_CTYPE_WIDTH;
        value &= lowbitsmask(SCR_CLIDR_BIT_CTYPE_WIDTH);
        type = value;
    }

    return type;
}

static inline void dump_cache_size(unsigned int level, unsigned int InD)
{
    int sets;
    int ways;
    int linesize;

    scr_get_cache_info(level, InD, &sets, &ways, &linesize);
    dprintf("  %d sets, %d ways, %d words per line.\n", sets, ways, linesize);
}

static void cache_dump_info(void)
{
    unsigned int level;
    unsigned int type;

    for (level = 1; level <= SCR_CLIDR_BIT_CTYPE_COUNT; level++) {
        type = scr_get_cache_type(level);
        if (type != SCR_CLIDR_CTYPE_NoCache) {
            dprintf("L%d Cache:\n", level);
        }

        switch (type) {
            case SCR_CLIDR_CTYPE_NoCache:
                break;

            case SCR_CLIDR_CTYPE_ICache:
                dprintf("   Instruction Cache.\n");
                dump_cache_size(level, 1);
                break;

            case SCR_CLIDR_CTYPE_DCache:
                dprintf("   Data Cache.\n");
                dump_cache_size(level, 0);
                break;

            case SCR_CLIDR_CTYPE_SIDCache:
                dprintf("   Seperated Instruction Cache and Data Cache.\n");
                dprintf("   Instruction Cache:\n");
                dump_cache_size(level, 1);
                dprintf("   Data Cache:\n");
                dump_cache_size(level, 0);
                break;

            case SCR_CLIDR_CTYPE_UCache:
                dprintf("    Unified Cache.\n");
                dump_cache_size(level, 0);
                break;

            default:
                dprintf("   Unkonwn Cache Type!\n");
                level = SCR_CLIDR_BIT_CTYPE_COUNT;
                break;
        }
    }
    dprintf("CLIDR_EL1.ICB:%x\n", (scr_read_CLIDR_EL1() >> SCR_CLIDR_BIT_ICB) & lowbitsmask(SCR_CLIDR_BIT_ICB_WIDTH));
    dprintf("CLIDR_EL1.LoUU:%x\n",
            (scr_read_CLIDR_EL1() >> SCR_CLIDR_BIT_LOUU) & lowbitsmask(SCR_CLIDR_BIT_LOUU_WIDTH));
    dprintf("CLIDR_EL1.LoC:%x\n", (scr_read_CLIDR_EL1() >> SCR_CLIDR_BIT_LOC) & lowbitsmask(SCR_CLIDR_BIT_LOC_WIDTH));
    dprintf("CLIDR_EL1.LoUIS:%x\n",
            (scr_read_CLIDR_EL1() >> SCR_CLIDR_BIT_LOUIS) & lowbitsmask(SCR_CLIDR_BIT_LOUIS_WIDTH));
}

static void maintain_data_cache_level(void (*maintain_setway)(uint64_t), unsigned int level)
{
    int          sets;
    int          ways;
    int          linesize;
    unsigned int waybegin;
    unsigned int setbegin;
    uint32_t     value = 0;

    scr_get_cache_info(level, 0, &sets, &ways, &linesize);

    assert(sets > 0 && ways > 0 && linesize > 0);

    waybegin = 32 - ln2(ways);
    setbegin = ln2(linesize * sizeof(int));

    while (ways-- > 0) {
        int j = sets;
        while (j-- > 0) {
            value = (ways << waybegin) | (j << setbegin) | (level << 1);
            maintain_setway(value);
        }
    }
}

static void maintain_data_cache_all(void (*maintain_setway)(uint64_t))
{
    unsigned int i;
    unsigned int ctype;

    for (i = 1; i <= SCR_CLIDR_BIT_CTYPE_COUNT; i++) {
        ctype = scr_get_cache_type(i);
        switch (ctype) {
            case SCR_CLIDR_CTYPE_NoCache:
                i = SCR_CLIDR_BIT_CTYPE_COUNT;
                break;

            case SCR_CLIDR_CTYPE_DCache:
            case SCR_CLIDR_CTYPE_UCache:
            case SCR_CLIDR_CTYPE_SIDCache:
                maintain_data_cache_level(maintain_setway, i);
                break;

            default:
                break;
        }
    }
    dsb();
}

void scr_clean_data_cache(void)
{
    maintain_data_cache_all(scr_write_DCCSW);
}

void scr_clean_invalidate_data_cache(void)
{
    maintain_data_cache_all(scr_write_DCCISW);
}

void scr_invalidate_data_cache(void)
{
    maintain_data_cache_all(scr_write_DCISW);
}
void scr_invalidate_instruction_cache(void)
{
    asm volatile("ic iallu\n\t");
}

void scr_clean_invalidate_data_cache_va(long VA)
{
    asm volatile("DC CIVAC,%[X0]\n\t" : : [X0] "r"(VA));
}

void flush_dcache_range(unsigned long start, unsigned long end)
{
    __asm__ __volatile__("mov x2,#64\n"
                         "sub	x3, x2, #1\n"
                         "bic	%0, %0, x3\n"
                         "1: dc  civac, %0\n"
                         "add	%0, %0, x2\n"
                         "cmp	%0, %1\n"
                         "b.lo	1b\n"
                         "dsb	sy\n" ::"r"(start),
                         "r"(end)
                         : "cc");
}

void scr_enable_cpu_cache(void)
{
    uint32_t value = 0;

    scr_invalidate_data_cache();
    scr_clean_invalidate_data_cache();
    value = scr_read_SCTLR_EL1();
    value |= bitmask(SCR_SCTLR_BIT_I) | bitmask(SCR_SCTLR_BIT_C);
    scr_write_SCTLR_EL1(value);
}

void scr_dump_info(void)
{
    cache_dump_info();
}

void set_csselr(uint32_t level, uint32_t type)
{
    uint32_t csselr = level << 1 | type;
    scr_write_CSSELR_EL1(csselr);
}

static uint32_t get_ccsidr(void)
{
    uint32_t ccsidr;
    ccsidr = scr_read_CCSIDR_EL1();
    return ccsidr;
}

uint32_t get_clidr(void)
{
    uint32_t clidr;
    clidr = scr_read_CLIDR_EL1();
    return clidr;
}
